package fifis.network.server;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
//import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;

/**
 *
 * @author porfiriopartida
 */
public abstract class FifisServer extends Thread{
    /**
     * Server Interface that receives messages from the server.
     */
    protected ServerReporter serverReporter;
    /**
     * Server Socket, the server that listens for connections.
     */
    protected ServerSocket serverSocket;
    /**
     * The name of the server if any
     */
    protected String serverName;
    /**
     * Port where this instance is listening.
     */
    protected int port;
    /**
     * Maximum allowed connections.
     */
//    protected int maxConnections;
    /**
     * List of clients. All of them.
     */
    public static HashMap<Integer, ClientHandle> clients = new HashMap<Integer, ClientHandle>();
//    public static ArrayList<ClientHandle> clients = new ArrayList();
    /**
     * Autoincremental clients count.
     */
    private static int CLIENTS_COUNT;
    
    protected boolean running = true;
    public static final String UPDATE_CLIENTS = "UPDATE_CLIENTS";
    /**
     * Creates a new server listening at the specified port and reporting to the specified interface.
     * <br />
     * The server name in this case is 'localhost'
     * 
     * @param serverReporter The interface to report.
     * @param port The port to listen.
     */
    public FifisServer(ServerReporter serverReporter, int port){
        this(serverReporter,port,"localhost");
    }
    /**
     * Constructs a server instance with the given name.
     * <br />
     * Allows 100 connections.
     * 
     * @param serverReporter The interface to report.
     * @param port The port to listen.
     * @param serverName The name of the server, if any.
     */
    public FifisServer(ServerReporter serverReporter, int port, String serverName){
        this.port = port;
        this.serverName = serverName;
        this.serverReporter = serverReporter;
        serverReporter.serverReport("Initializing..");
    }
//    /**
//     * Constructs the server instance with the maximum connection allowed specified.
//     * 
//     * @param serverReporter The interface to report.
//     * @param port The port to listen.
//     * @param serverName The name of the server, if any.
//     * @param maxConnections The maximum allowed connections.
//     */
//    public FifisServer(ServerReporter serverReporter, int port, String serverName, int maxConnections){
//        this.port = port;
//        this.serverName = serverName;
//        this.serverReporter = serverReporter;
//        this.maxConnections = maxConnections;
//        serverReporter.serverReport("Initializing..");
//    }
    /**
     * Starts the server main loop, creating client instances for every connection.
     */
    @Override
    public void run(){
        try {
            beforeStart();
        
            serverReporter.serverReport("Starting server..");
            serverSocket = new ServerSocket(port);
            
            afterStart();
        } catch (IOException ex) {
            //serverReporter.serverReportError("====================");
            serverReporter.serverReportError("FifisServer.init()");
            serverReporter.serverReportError("serverSocket = new ServerSocket("+port+");");
            serverReporter.serverReportError("Server init error: ");
            serverReporter.serverReportError(ex.getMessage());
            //serverReporter.serverReportError("====================");
            //ex.printStackTrace();
        }
        serverReporter.serverReport("Starting the listening loop..");
        while(running){
            Socket c = null;
            try {
                c = serverSocket.accept();
            } catch (IOException ex) {
                if(running){
                    serverReporter.serverReportError("FifisServer.init()");
                    serverReporter.serverReportError("c = serverSocket.accept();");
                    serverReporter.serverReportError("Server init error: ");
                    serverReporter.serverReportError(ex.getMessage());
                    continue;
                }
            }
            ClientHandle ch = new ClientHandle(this,c,CLIENTS_COUNT);
            ch.start();
//            clients.add(ch);
            clients.put(CLIENTS_COUNT,ch);
            
            CLIENTS_COUNT++;
        }
    }
    /**
     * Sends a message to a single client.
     * 
     * @param id The id of the client to send.
     * @param msg The message to cast.
     * @return true if the message was sent.
     */
    public boolean singleCast(int id, String msg){
        serverReporter.serverReport("Single Casting to "+id);
        ClientHandle client = clients.get(id);
//        if(id>=clients.size()){
//            serverReporter.serverReport("Single cast failed");
//            return false;
//        }
        if(client==null){
            serverReporter.serverReport("Single cast failed");
            return false;
        }
        clients.get(id).send(msg);
        serverReporter.serverReport(msg);
        return true;
    }
    public boolean clientIsConnected(int id){
        //System.err.println("FifisServer.clientIsConnected :: "+id);
        try{
            if(id>=clients.size()){
                return false;
            }
            ClientHandle ch = clients.get(id);

            return clientIsConnected(ch);
        }catch(Exception e){
            return false;
        }
    }
    protected boolean clientIsConnected(ClientHandle ch){
        if(ch==null) return false;
        if(ch.getSocket().isClosed()) return false;
        
        return ch.getSocket().isConnected();
    }
    public void broadCast(String msg){
        serverReporter.serverReport("Broadcasting...");
//        for(int i=0;i<clients.size(); i++){
//            ClientHandle ch = clients.get(i);
//            if(clientIsConnected(ch))
//                ch.send(msg);
//        }
        Collection<ClientHandle> clientList = clients.values();
        for(ClientHandle client : clientList){
            if(clientIsConnected(client))
                client.send(msg);
        }
    }
    public void broadCast(String msg, int except){
        serverReporter.serverReport("Broadcasting... except : "+ except);
//        for(int i=0;i<clients.size(); i++){
//            ClientHandle ch = clients.get(i);
//            if(clientIsConnected(ch) && ch.getClientId()!=except)
//                ch.send(msg);
//        }
        Collection<ClientHandle> clientList = clients.values();
        for(ClientHandle client : clientList){
            if(clientIsConnected(client) && client.getClientId()!=except)
                client.send(msg);
        }
    }
    public void groupCast(String group, String msg){
        serverReporter.serverReport("Group Casting..."+group);
//        for(int i=0;i<clients.size(); i++){
//            ClientHandle ch = clients.get(i);
//            if(clientIsConnected(ch) && group.equals(ch.getGroup())){
//                ch.send(msg);
//            }
//        }
        Collection<ClientHandle> clientList = clients.values();
        for(ClientHandle client : clientList){
            if(clientIsConnected(client) && group.equals(client.getGroup())){
                client.send(msg);
            }
        }
    }
    /**
     * You can handle here the 
     * 
     * @param id
     * @param msg 
     */
    public abstract void handle(int id, String msg);
    /**
     * Called before the ServerSocket inits: 
     * <br />
     * beforeStart();
     * <br />
     * new ServerSocket(port);
     */
    protected abstract void beforeStart();
    /**
     * Called after the ServerSocket inits: 
     * <br />
     * new ServerSocket(port);
     * <br />
     * afterStart();
     */
    protected abstract void afterStart();
    /**
     * Notification of a new client connected.
     * 
     * @param c The ClientHandle instance (client)
     */
    public abstract void clientConnected(ClientHandle c);
    /**
     * Notification of a disconnected client.
     * 
     * @param c The disconnected client.
     */
    public abstract void clientDisconnected(ClientHandle c);
    /**
     * Gets the reporter interface binded to this server.
     * 
     * @return The server reporter to which this server sends its notifications.
     */
    public ServerReporter getReporter(){return serverReporter; }
}
